package com.duobaoyu.dby.supply.message.listener.hander.admin;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.duobaoyu.dby.channel.adapter.enums.ChannelCodeEnum;
import com.duobaoyu.dby.channel.adapter.enums.biz.order.OrderExceptionTypeEnum;
import com.duobaoyu.dby.channel.adapter.enums.biz.order.OrderStatusEnum;
import com.duobaoyu.dby.channel.adapter.util.ChannelCodeUtils;
import com.duobaoyu.dby.channel.common.util.SupplyCollectionUtils;
import com.duobaoyu.dby.common.core.constant.CommonConstants;
import com.duobaoyu.dby.common.mybatis.util.EnumUtils;
import com.duobaoyu.dby.saas.product.common.remote.dto.PlatformPriceCheckDTO;
import com.duobaoyu.dby.supply.biz.client.boundary.order.OrderBoundaryClient;
import com.duobaoyu.dby.supply.biz.client.boundary.order.bean.bo.OrderBO;
import com.duobaoyu.dby.supply.biz.client.boundary.order.bean.bo.OrderConfirmResultBO;
import com.duobaoyu.dby.supply.biz.client.boundary.order.bean.bo.OrderQueryInBO;
import com.duobaoyu.dby.supply.biz.client.boundary.order.bean.bo.UpdateOrderBO;
import com.duobaoyu.dby.supply.biz.client.product.ProductCenterApiClient;
import com.duobaoyu.dby.supply.biz.client.product.bean.bo.PlatformPriceCheckBO;
import com.duobaoyu.dby.supply.biz.constant.OperateSourceConstants;
import com.duobaoyu.dby.supply.biz.constant.OrderOperateConstants;
import com.duobaoyu.dby.supply.biz.enums.*;
import com.duobaoyu.dby.supply.biz.producer.SupplyChainMQProducer;
import com.duobaoyu.dby.supply.biz.producer.constant.SupplyChainMQConstants;
import com.duobaoyu.dby.supply.biz.producer.message.OrderRepairMessage;
import com.duobaoyu.dby.supply.biz.service.config.OperationLogService;
import com.duobaoyu.dby.supply.biz.service.order.OrderRepairService;
import com.duobaoyu.dby.supply.biz.service.order.OrderService;
import com.duobaoyu.dby.supply.biz.service.order.event.OrderConfirmSuccessEvent;
import com.duobaoyu.dby.supply.biz.util.DingDingExtUtils;
import com.duobaoyu.dby.supply.common.biz.bo.order.OrderRepairBO;
import com.duobaoyu.dby.supply.common.biz.bo.order.OrderRepairRecordBO;
import com.duobaoyu.dby.supply.common.biz.bo.order.UpdateOrderRepairBO;
import com.duobaoyu.dby.supply.common.biz.bo.order.UpdateOrderRepairRecordBO;
import com.duobaoyu.dby.supply.common.biz.dto.config.OperationLogSaveDTO;
import com.duobaoyu.dby.supply.common.biz.dto.log.LogDiff;
import com.duobaoyu.dby.supply.core.cache.service.RedissonService;
import com.duobaoyu.dby.supply.core.dinger.annotation.DingerTalk;
import com.duobaoyu.dby.supply.core.dinger.util.DingDingUtils;
import com.duobaoyu.dby.supply.core.rocketmq.handler.AbstractClusterMessageHandler;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 订单补单消息
 *
 * @author chenms
 * @date 2023/10/18 9:41 AM
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SupplyChainOrderRepairMessageHandler extends AbstractClusterMessageHandler<OrderRepairMessage> {

    private final OrderService orderService;
    private final RedissonService redissonService;
    private final OrderRepairService orderRepairService;
    private final OrderBoundaryClient orderBoundaryClient;
    private final OperationLogService operationLogService;
    private final TransactionTemplate transactionTemplate;
    private final SupplyChainMQProducer supplyChainMQProducer;
    private final ProductCenterApiClient productCenterApiClient;

    @Override
    public String getTopic() {
        return SupplyChainMQConstants.SupplyChainTopic.SUPPLY_CHAIN_ADMIN_TOPIC;
    }

    @Override
    public String getTag() {
        return SupplyChainMQConstants.SupplyChainTopic.SupplyChainAdminTag.ORDER_REPAIR_TAG;
    }

    @Override
    @DingerTalk(title = "供应链订单补单消息Consumer")
    public boolean messageHandler(OrderRepairMessage orderRepairMessage, MessageExt messageExt) {
        log.info("收到补单消息: {}", JSON.toJSONString(orderRepairMessage));
        String orderSn = orderRepairMessage.getOrderSn();
        if (StrUtil.isBlank(orderSn)) {
            DingDingExtUtils.sendMarkdownMsg("补单MQ消息体订单号缺失");
            return true;
        }
        return redissonService.tryLock(RedisKeys.LOCK_REDIS.formatKey(LockKeys.REPAIR_ORDER.formatKey(orderSn)),() -> true,() -> {
            Tuple validResult = this.validData(orderSn);
            if (Objects.isNull(validResult)) {
                return true;
            }
            OrderRepairBO orderRepair = validResult.get(CommonConstants.ZERO);
            OrderBO order = validResult.get(CommonConstants.ONE);
            // 如果存在渠道交易单号说明预下单成功, 执行确认订单流程.否则执行提交并确认
            boolean isPreOrderConfirm = StringUtils.isNotBlank(order.getChannelTradeOrderSn());
            orderRepair.setIsConfirmOrder(isPreOrderConfirm);
            OrderRepairRecordBO orderRepairRecord = orderRepairService.saveRepairRecord(orderRepair);
            log.info("生成补单订单号与渠道订单号新映射关系: 旧单号={},新单号{}", orderRepairRecord.getOrderSn(), orderRepairRecord.getRepairOrderSn());
            // 更改渠道接口的外部订单号, 重新提交
            order.setOrderTradeSn(orderRepairRecord.getRepairOrderSn());
            OrderConfirmResultBO orderConfirmResult;
            if (isPreOrderConfirm) {
                if(log.isDebugEnabled()) {
                    log.debug("订单已预下单成功,开始渠道确认 order={} ", JSON.toJSONString(order));
                }
                orderConfirmResult = orderBoundaryClient.confirmPreOrder(Lists.newArrayList(order));
            } else {
                if(log.isDebugEnabled()) {
                    log.debug("订单无预下单成功记录,开始渠道提交并确认 order={} ", JSON.toJSONString(order));
                }
                orderConfirmResult = orderBoundaryClient.submitAndConfirmOrder(Lists.newArrayList(order));
            }
            if(log.isDebugEnabled()) {
                log.debug("补单结果: {}", JSON.toJSONString(orderConfirmResult));
            }

            // 处理补单失败的订单
            List<OrderConfirmResultBO.ConfirmFailureBO> failedList = orderConfirmResult.getFailedList();
            Optional.ofNullable(SupplyCollectionUtils.getSingleDataFirst(failedList)).ifPresent(this::handleRepairFailureData);
            // 处理补单成功的订单
            List<OrderConfirmResultBO.ConfirmSuccessBO> confirmSuccessList = orderConfirmResult.getConfirmSuccessList();
            Optional.ofNullable(SupplyCollectionUtils.getSingleDataFirst(confirmSuccessList)).ifPresent(item -> this.handleRepairSuccessData(item, orderRepairRecord, order));
            return true;
        });
    }

    /**
     * 校验补单数据
     *
     * @param orderSn
     * @return
     */
    private Tuple validData(String orderSn) {
        OrderRepairBO orderRepair = orderRepairService.getByOrderSn(orderSn, true);
        if (Objects.isNull(orderRepair)) {
            log.info("订单补单记录不存在: {}", orderSn);
            DingDingUtils.sendMarkdownMsg(String.format("补单消息处理结束,补单记录不存在 [%s]", orderRepair.getOrderSn()));
            return null;
        }
        boolean valid = EnumUtils.eq(OrderRepairStatusEnum.REPAIRING, orderRepair.getRepairStatus());
        if (!valid) {
            log.info("补单记录状态不正确,不能进行补单操作: orderSn={},repairStatus={}", orderSn, orderRepair.getRepairStatus());
            return null;
        }
        boolean existsRepairingRecord = Optional.ofNullable(orderRepair.getOrderRepairRecords()).orElse(Collections.emptyList()).stream()
                .anyMatch(item -> EnumUtils.eq(OrderRepairStatusEnum.REPAIRING, item.getRepairStatus()));
        if (existsRepairingRecord) {
            log.info("存在正在补单中明细记录,不能重复补单: orderSn={}", orderSn);
            return null;
        }

        List<OrderBO> orderList = orderService.list(OrderQueryInBO.builder()
                .orderSnList(Lists.newArrayList(orderSn))
                .orderStatusList(Lists.newArrayList(OrderStatusEnum.FAILURE.getValue()))
                .returnProduct(true)
                .returnConsignee(true)
                .build());
        OrderBO order = SupplyCollectionUtils.getSingleDataFirst(orderList);
        if (Objects.isNull(order)) {
            log.info("补单消息单号对应的订单不存在, 更新补单状态为失败: {}", orderSn);
            // 更新为补单失败
            UpdateOrderRepairBO updateOrderRepair = UpdateOrderRepairBO.builder().newStatus(OrderRepairStatusEnum.REPAIR_FAILED.getValue()).failDesc("订单不存在")
                    .exceptionType(OrderExceptionTypeEnum.OTHER.getValue()).orderSnList(Lists.newArrayList(orderSn)).build();
            orderRepairService.updateOrderRepairStatusAndErrorInfo(updateOrderRepair);
            return null;
        }
        List<PlatformPriceCheckDTO> platformPriceCheckList = order.getOrderProductList().stream().map(item -> PlatformPriceCheckDTO.builder().platformPrice(item.getPlatformPrice()).skuCode(item.getSkuCode()).build()).collect(Collectors.toList());
        PlatformPriceCheckBO platformPriceCheck = productCenterApiClient.checkPlatformPriceAndState(platformPriceCheckList);
        if (platformPriceCheck.existsErrorSku()) {
            if(log.isDebugEnabled()) {
                log.debug("补单订单商品成本价和状态校验失败, 更新补单状态为失败: orderSn={}, 校验失败数据={}", orderSn, JSON.toJSONString(platformPriceCheck));
            }
            // 更新为补单失败
            UpdateOrderRepairBO updateOrderRepair = UpdateOrderRepairBO.builder().newStatus(OrderRepairStatusEnum.REPAIR_FAILED.getValue())
                    .failDesc(platformPriceCheck.errorMessage(CommonConstants.HUNDRED * CommonConstants.TWO))
                    .exceptionType(OrderExceptionTypeEnum.OTHER.getValue()).orderSnList(Lists.newArrayList(orderSn)).build();
            orderRepairService.updateOrderRepairStatusAndErrorInfo(updateOrderRepair);
            LogDiff logDiff = new LogDiff("失败原因", null, platformPriceCheck.errorMessage(null));
            operationLogService.asyncSave(OperationLogSaveDTO.builder().logType(OperationLogTypeEnum.ORDER.getValue())
                    .source(OperateSourceConstants.SYSTEM).bizKey(orderSn).scene(OrderOperateConstants.REPAIR_ORDER_FAILED).diffList(Lists.newArrayList(logDiff)).build());
            return null;
        }
        Tuple result = new Tuple(orderRepair, order);
        return result;
    }


    /**
     * 渠道补单请求成功数据处理
     *
     * @param confirmSuccess
     * @param orderRepairRecord
     * @param order
     */
    private void handleRepairSuccessData(OrderConfirmResultBO.ConfirmSuccessBO confirmSuccess, OrderRepairRecordBO orderRepairRecord, OrderBO order) {
        String orderSn = confirmSuccess.getOrderSn();
        try {
            transactionTemplate.executeWithoutResult(transactionStatus -> {
                // 更改补单状态
                UpdateOrderRepairRecordBO updateOrderRepairRecord = UpdateOrderRepairRecordBO.builder()
                        .orderSn(confirmSuccess.getOrderSn())
                        .channelTradeOrderSn(confirmSuccess.getChannelTradeOrderSn())
                        .newStatus(OrderRepairStatusEnum.REPAIR_COMPLETE.getValue()).build();
                orderRepairService.updateRepairRecordStatusAndErrorInfo(updateOrderRepairRecord);

                // 更新订单
                UpdateOrderBO updateOrder = new UpdateOrderBO();
                updateOrder.setOrderSn(order.getOrderSn());
                updateOrder.setOrderTag(order.removeTag(OrderTagEnum.REPAIRING.getValue()));
                Integer status = Objects.nonNull(confirmSuccess.getStatus()) ? confirmSuccess.getStatus() : OrderStatusEnum.RECHARGE.getValue();
                updateOrder.setOrderStatus(Pair.of(order.getOrderStatus(), status));
                updateOrder.setOrderTradeSn(orderRepairRecord.getRepairOrderSn());
                String channelOrderSn = Optional.ofNullable(confirmSuccess.getChannelTradeOrderSn()).orElse(order.getChannelTradeOrderSn());
                updateOrder.setChannelOrderSn(channelOrderSn);
                updateOrder.setChannelTradeOrderSn(confirmSuccess.getChannelTradeOrderSn());
                updateOrder.setSource(OperateSourceConstants.SYSTEM);
                updateOrder.setDescription(OrderOperateConstants.REPAIR_ORDER_SUCCESS);
                ChannelCodeEnum orderChannelEnum = ChannelCodeEnum.getChannelCodeEnumByCode(order.getChannelCode());
                if (ChannelCodeUtils.supportImmediateAfterSalesChannelsAfterConfirmOrder().contains(orderChannelEnum)) {
                    updateOrder.setAfterSalesAllowed(Boolean.TRUE);
                }
                updateOrder.setLogChangeItemList(order.diff(updateOrder).getDiffs());
                boolean b = orderService.updateOrderByOrderSn(updateOrder);
                if (!b) {
                    log.warn("补单成功但更新订单信息失败. 更新数据={}", JSON.toJSONString(updateOrder));
                    DingDingExtUtils.sendMarkdownMsg(String.format("补单成功但更新订单数据失败: orderSn=%s", orderSn));
                } else {
                    // 状态变更,给业务方推送消息
                    if (!Objects.equals(order.getOrderStatus(), status)) {
                        supplyChainMQProducer.orderAlterSend(order.getSupplyTradeSn(), order.getServerName());
                    }
                    SpringUtil.publishEvent(new OrderConfirmSuccessEvent(order.update(updateOrder), confirmSuccess));
                }
            });
        } catch (Exception e) {
            log.warn("处理补单成功的订单异常: orderSn={}, 补单结果={},", orderSn, JSON.toJSONString(confirmSuccess), e);
            DingDingExtUtils.sendMarkdownMsg(String.format("补单成功数据处理异常: orderSn=%s", orderSn));
        }
    }


    /**
     * 渠道补单失败数据处理
     *
     * @param confirmFailure
     */
    private void handleRepairFailureData(OrderConfirmResultBO.ConfirmFailureBO confirmFailure) {
        String errorMessage = confirmFailure.getTruncationErrorInfo();
        String orderSn = confirmFailure.getOrderSn();
        UpdateOrderRepairRecordBO updateOrderRepairRecord = UpdateOrderRepairRecordBO.builder()
                .orderSn(orderSn)
                .channelTradeOrderSn(confirmFailure.getChannelTradeOrderSn())
                .newStatus(OrderRepairStatusEnum.REPAIR_FAILED.getValue())
                .failCode(confirmFailure.getErrorCode())
                .exceptionType(OrderExceptionTypeEnum.convertFromGlobalErrorCode(confirmFailure.getErrorCode()).getValue())
                .failDesc(errorMessage).build();
        orderRepairService.updateRepairRecordStatusAndErrorInfo(updateOrderRepairRecord);
        List<LogDiff> logDiffs = Lists.newArrayList(new LogDiff("失败原因", null, confirmFailure.getErrorMessage()));
        OperationLogSaveDTO logSaveDTO = OperationLogSaveDTO.builder().logType(OperationLogTypeEnum.ORDER.getValue()).diffList(logDiffs)
                .bizKey(orderSn).scene(OrderOperateConstants.REPAIR_ORDER_FAILED).source(OperateSourceConstants.SYSTEM).build();
        operationLogService.asyncSave(logSaveDTO);
    }
}
